function Player(init_x, init_y)
    local ANIMATION_TIME = .05
    local POWER_DURATION = .3
    local requiredPowerScore = 20

    local timeSinceLastAnimate = 0
    local remainingPowerDrawTime = 0
    local lastPowerScore = 0

    local self = {}

    local function correctPlayerLatitude(self)
        local body = self.fixture:getBody()
        local wcx, wcy = body:getWorldCenter()

        -- If the center of the player is not in the middle of the screen (hh),
        -- then adjust the velocity, so the player stays centered
        if (round(wcy) ~= 0) then
            local yv = 0
                
            -- If the player is above the center then something hit him to make him
            -- move upward. In this case the player should slow down.
            if (wcy > 0) then
                yv = -wcy/2
            end
            
            -- Keep any x velocity, but set the y velocity as appropriate to keep the player in the center of the screen
            local lvx, lvy = body:getLinearVelocity()
            body:setLinearVelocity(lvx, yv);
        end
    end

    local function onDrawPower(index, xOff, yOff, xFlip, yFlip)
        if (remainingPowerDrawTime <= 0) then return end
        MOAIGfxDevice.setPenColor(1, 1, 1, 1)
        MOAIGfxDevice.setPenWidth(2)

        -- Draw the concentric circles for the power
        local wcx,wcy = self.fixture:getBody():getWorldCenter()
        for i=1,.1,-.2 do
            MOAIDraw.drawCircle(wcx, wcy, (POWER_DURATION - remainingPowerDrawTime)*math.log(i)*SCREEN_WIDTH);
        end
    end

    local function doPower()
        if (getScore() - lastPowerScore < requiredPowerScore) then return end

        -- Stop the player from spinning and set him upright
        local body = self.fixture:getBody()
        body:setAngularVelocity(0)
        local wcx, wcy = body:getWorldCenter()
        -- I'm not sure why setting wcy without the extra .05 pushes the player down the screen.
        -- Adding the .05 seems to keep the player at the same latitude.
        body:setTransform(wcx, wcy + .05, 0)

        -- set the power time
        remainingPowerDrawTime = POWER_DURATION
        lastPowerScore = getScore()
    end

    local function update()
        correctPlayerLatitude(self)

        local body = self.fixture:getBody()
        local x,y = body:getWorldCenter()
        local keyboard = MOAIInputMgr.device.keyboard

        -- handle input
        if keyboard:keyIsDown(97) or keyboard:keyIsDown(65) then --left ("a" or "A")
            body:applyLinearImpulse(-.2, 0, x, y)
        elseif keyboard:keyIsDown(100) or keyboard:keyIsDown(68) then --right ("d" or "D")
            body:applyLinearImpulse(.2, 0, x, y)
        end

        if keyboard:keyIsDown(32) then -- space
          if (0 == remainingPowerDrawTime) then
              doPower()
          end
        end

        remainingPowerDrawTime = math.max(remainingPowerDrawTime - elapsed, 0)

        -- animate the sprite
        timeSinceLastAnimate = timeSinceLastAnimate + elapsed
        if (timeSinceLastAnimate > ANIMATION_TIME) then
            self.prop:setIndex(((self.prop:getIndex() + 1) % 3)+1)
            timeSinceLastAnimate = 0
        end
    end

    -- CONSTRUCTOR

    -- setup box2d body
    local body = world:addBody(MOAIBox2DBody.DYNAMIC)
    local fixture = body:addRect(-.4, .4, .4, -.5)
    fixture:setDensity(0.8)
    fixture:setFriction ( 2 )
    fixture:setFilter ( 0x03 )
    fixture:setCollisionHandler ( onPlayerCollide, MOAIBox2DArbiter.BEGIN, 0x01 )

    body:resetMassData()
    body:setTransform(3, 2)

    self.fixture = fixture;

    -- setup sprite
    local spriteSheet = MOAITileDeck2D.new()
    spriteSheet:setTexture("../assets/gfx/player-sprite.png")
    spriteSheet:setSize(3,1)
    spriteSheet:setRect(-0.5, -0.5, 0.5, 0.5)

    local prop = MOAIProp2D.new()
    prop:setDeck(spriteSheet)
    layer:insertProp(prop)

    prop:setAttrLink(MOAIProp2D.INHERIT_TRANSFORM, body, MOAIProp2D.TRANSFORM_TRAIT)
    self.prop = prop

    -- setup a scriptDeck the size of the screen for drawing the power rings
    local powerScriptDeck = MOAIScriptDeck.new ()
    powerScriptDeck:setRect(-SCREEN_WIDTH/2, -SCREEN_HEIGHT/2, SCREEN_HEIGHT/2, SCREEN_HEIGHT/2)
    powerScriptDeck:setDrawCallback(onDrawPower)

    local powerProp = MOAIProp2D.new()
    powerProp:setDeck(powerScriptDeck)
    layer:insertProp(powerProp)

    return {
        update = update,
    }
end